package com.bob.clan.wxconnector.controller;


import com.bob.clan.wxconnector.DTO.WebAuthorMsgDTO;
import com.bob.clan.wxconnector.component.MpComponent;
import com.bob.clan.wxconnector.component.WebAuthorComponent;
import com.bob.clan.wxconnector.config.SysConfig;
import com.bob.clan.wxconnector.entity.Fans;
import com.bob.clan.wxconnector.entity.Mp;
import com.bob.clan.wxconnector.exception.LoginException;
import com.bob.clan.wxconnector.exception.MpException;
import com.bob.clan.wxconnector.service.IFansService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Set;

@Slf4j
@Controller
public class WebAuthorizeController {
    @Autowired
    private MpComponent mpComponent;
    @Autowired
    private WebAuthorComponent webAuthorComponent;
    @Autowired
    private IFansService fansService;
    @Autowired
    private SysConfig sysConfig;


    private static Integer lock_flag = 0;

    //保存wsSessionId与时间的键值对，使二维码一定时间内只能使用一次，防止多人扫码
    //保存sessionId与时间的键值对原因：当wsapi重启时，wsId会从0开始，若QYH不重启，则扫码失败，设置二维码扫描时间为15分钟，15分钟后可以再次被扫描
    private static HashMap<String, Long> wsIdMap = new HashMap<String, Long>();
    private Hashtable<String, Long> codeMap = new Hashtable<String, Long>();


    //打开扫码登录页，该页的二维码是微信鉴权中心的userBaseURL

    /**
     * 本处是手机端H5以扫码的方式登录到鉴权中心后，指定的回调地址
     * @param userType
     * @param params
     * @param code
     * @param state
     * @param request
     * @param response
     * @param model
     * @return
     */
    @RequestMapping("/webAuthorize/qrScan/{userType}")
    public String qrScan(@PathVariable("userType") String userType, String params, String code, String state, HttpServletRequest request, HttpServletResponse response, Model model) {
        log.info("已进入scanQrCodeLoginManage方法，请求参数：----params----" + params + "----code----" + code + "-----state-----" + state);
        try {
            if(this.isValidCode(code)) {
                String sysCode = this.getParamsFromState(params, 0);
                String wsSessionId = this.getParamsFromState(params, 1);
                //wsSessionId一定时间内只能使用一次，防止多人扫码
                if (wsIdMap.get(wsSessionId) != null && wsIdMap.get(wsSessionId) > System.currentTimeMillis()) {
                    model.addAttribute("error",new LoginException(5001));
                    log.error("当前二维码已被扫描！");
                    return "error";
                } else {
                    //已被扫描的二维码在此时间点前不可被扫描
                    wsIdMap.put(wsSessionId, System.currentTimeMillis() + 15 * 60 * 1000);
                }
                Mp mp = mpComponent.getBySysCode(sysCode);
                log.info("code获取openId方法将要执行（getBaseOpenId）");
                WebAuthorMsgDTO userBaseMsg = webAuthorComponent.getUserBaseMsg(mp, code);
                String openid = userBaseMsg.getOpenid();
                log.debug("get sns_base_user, successful! openid =" + openid);
                Fans fans = fansService.getFans(openid);
                if (fans == null) {
                    if (userType.equals("userBase")) {
                        log.debug("未授权过的客户，重新跳转至授权获取userInfo页---sysCode----" + sysCode + "-------wsSessionId--------" + wsSessionId + "------mpId----" + mp.getId());
                        wsIdMap.remove(wsSessionId);
                        String redirectUrl = webAuthorComponent.getUserInfoUrl(mp, wsSessionId);
                        //return "redirect:"+redirectUrl;
                        log.debug("新用户到手动授权页后，再次跳转至鉴权中心："+redirectUrl);
                        model.addAttribute("contextPath", request.getContextPath()); // 如果需要在视图中使用
                        model.addAttribute("WXuserInfoURL",redirectUrl);
                        model.addAttribute("wsSessionId",wsSessionId);
                        model.addAttribute("mp",mp);
                        return "qrLogin/firstAuthLogin";
                    } else if (userType.equals("userInfo")) {
                        fans = webAuthorComponent.getFansInfo(userBaseMsg.getAccess_token(), openid);
                        fansService.save(fans);
                    }
                }
                log.debug("已授权过的客户，即将跳转到登录确认页---sysCode----" + sysCode + "-------wsSessionId--------" + wsSessionId + "------mpId----" + mp.getId() + "------fansId-------" + fans.getId());
                // 现在你可以使用contextPath变量了
                model.addAttribute("contextPath", request.getContextPath()); // 如果需要在视图中使用
                model.addAttribute("webSocketPath",sysConfig.getServerPath());
                model.addAttribute("sysCode",sysCode);
                model.addAttribute("sysName",mp.getName());
                model.addAttribute("wsSessionId",wsSessionId);
                model.addAttribute("fans",fans);
                String timestamp = String.valueOf(System.currentTimeMillis());
                model.addAttribute("timestamp",timestamp);
                String signature = getSHA256(fans,timestamp,mp);
                model.addAttribute("signature",signature);
                return "qrLogin/confirmLogin";
            } else {
                log.error(code + ",the code had used, wechat sb bug review!");
                model.addAttribute("error",new LoginException(4001));
                return "error";
            }
        } catch (MpException mpException) {
            log.error(mpException.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }
        model.addAttribute("error",new LoginException(4002));
        return "error";
    }

    /**
     * 登录中心与业务系统的登录信息加官，业务系统收到签名后，可验证登录信息源是登录中心，否则可以阻止非法登录
     * @param fans
     * @param timestamp
     * @return
     */
    private String getSHA256(Fans fans, String timestamp,Mp mp) {
        String input = fans.getOpenid()+timestamp+mp.getSysCode();
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] messageDigest = md.digest(input.getBytes());
            StringBuilder hexString = new StringBuilder();
            for (byte b : messageDigest) {
                hexString.append(String.format("%02x", b));
            }
            return hexString.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    //因企鹅平台的bug,短时间内可能发送的code是已使用的，此时将导致本次code因重复而无效；
    private boolean isValidCode(String code) {
        if (codeMap.get(code) == null) {
            log.debug("new code," + codeMap.size() + ",hashCode:");
            codeMap.put(code, System.currentTimeMillis());
            synchronized (WebAuthorizeController.lock_flag) {
                if (codeMap.size() > 200) {
                    flushCodeMap(5);
                }
            }
            return true;
        } else {
            long codeTime = codeMap.get(code);
            long ms = System.currentTimeMillis() - codeTime;
            log.debug("old code, had user at " + ms + "ms ");
            return false;
        }
    }

    //防内存泄露，定期清理静态数据
    private void flushCodeMap(int keepSecends) {
        Long curTime = System.currentTimeMillis();
        Hashtable<String, Long> newCodeMap = new Hashtable<String, Long>();
        Set<String> codeKey = codeMap.keySet();
        log.debug("snsCodeMap size:" + codeMap.size() + ",will do flush! hashCode:" + codeMap.hashCode());
        for (String code : codeKey) {
            Long codeTime = codeMap.get(code);
            log.debug("code=" + code + ",curTime=" + curTime + ",codeTime=" + codeTime);
            if (curTime - codeTime < keepSecends * 1000) {
                newCodeMap.put(code, codeTime);
            }
        }
        codeMap = newCodeMap;
        log.debug("flush done, snsCodeMap size:" + codeMap.size() + ",hashCode:" + codeMap.hashCode());
    }

    private String getParamsFromState(String params, int type) {
        String[] paramsArray = params.split("_");
        //业务系统编码
        String busSysCode = paramsArray[0];
        //websocket系统SessionId，用于标识登录者
        String wsSessionId = paramsArray[1];
        return paramsArray[type];
    }
    //对参数进行处理

}
